Knihy programovacích jazyků vysvětlují, že typy hodnot jsou vytvářeny na zásobníku a typy odkazů jsou vytvářeny na haldě, aniž by vysvětlovaly, o jaké dvě věci jde. Nečetl jsem jasné vysvětlení. Chápu, co je to stack. Ale, Kde a co jsou (fyzicky v paměti skutečného počítače)? Do jaké míry jsou řízeny operačním systémem nebo jazykem? Jaký je jejich rozsah? Co určuje velikost každého z nich? Co dělá člověka rychlejším?
2020-12-07 21:36:42
Zásobník je paměť vyčleněná jako stírací prostor pro vlákno provedení. Když je funkce volána, blok je vyhrazen v horní části zásobníku pro místní proměnné a některá data účetnictví. Když se tato funkce vrátí, blok se nepoužívá a lze jej použít při příštím volání funkce. Zásobník je vždy rezervován v pořadí LIFO (last in first out); naposledy rezervovaný blok je vždy další blok, který má být uvolněn. Díky tomu je sledování zásobníku opravdu jednoduché; uvolnění bloku ze zásobníku není nic jiného než úprava jednoho ukazatele. Halda je paměť vyhrazená pro dynamickou alokaci. Na rozdíl od zásobníku neexistuje žádný vynucený vzor pro přidělování a uvolňování bloků z haldy; blok můžete kdykoli přidělit a kdykoli uvolnit. Díky tomu je mnohem složitější sledovat, které části haldy jsou v daném okamžiku přiděleny nebo volné; Existuje mnoho vlastních alokátorů haldy k vyladění výkonu haldy pro různé vzory použití. Každé vlákno získá zásobník, zatímco pro aplikaci je obvykle jen jedna hromada (i když není neobvyklé mít několik hromád pro různé typy přidělení). Přímé zodpovězení otázek: Do jaké míry jsou řízeny operačním systémem nebo jazykovým modulem runtime? Když je vlákno vytvořeno, OS přiděluje zásobník pro každé vlákno na úrovni systému. Obvykle se operační systém volá jazykovým modulem runtime, aby přidělil haldu pro aplikaci. Jaký je jejich rozsah? Zásobník je připojen k vláknu, takže když vlákno opustí, zásobník je uvolněn. Halda je obvykle přidělena při spuštění aplikace modulem runtime a je uvolněna, když je aplikace (technicky zpracována) ukončena. Co určuje velikost každého z nich? Velikost zásobníku se nastaví při vytvoření vlákna. Velikost haldy je nastavena při spuštění aplikace, ale může se zvětšovat podle potřeby místa (alokátor požaduje více paměti z operačního systému). Co dělá člověka rychlejším? Zásobník je rychlejší, protože přístupový vzor umožňuje triviální alokaci a uvolnění paměti z ní (ukazatel / celé číslo se jednoduše zvýší nebo sníží), zatímco halda má mnohem složitější účetnictví zapojené do alokace nebo deallocation. Každý bajt v zásobníku má také tendenci být opakovaně používán velmi často, což znamená, že má tendenci být mapován do mezipaměti procesoru, což je velmi rychlé. Dalším výkonovým hitem haldy je to, že halda, která je většinou globálním zdrojem, musí být obvykle bezpečná pro více vláken, tj. Každá alokace a deallocation musí být - obvykle - synchronizována s „všemi“ ostatními haldy v programu. Jasná ukázka: Zdroj obrázku: vikashazrati.wordpress.com | Zásobník: Uloženo v paměti RAM počítače jako hromada. Proměnné vytvořené v zásobníku vyjdou z rozsahu a budou automaticky uvolněny. Mnohem rychlejší alokace ve srovnání s proměnnými na haldě. Implementováno se skutečnou datovou strukturou zásobníku. Ukládá místní data, zpáteční adresy, slouží k předávání parametrů. Může mít přetečení zásobníku, když je použito příliš mnoho zásobníku (většinou z nekonečné nebo příliš hluboké rekurze, velmi velké alokace). Data vytvořená v zásobníku lze použít bez ukazatelů. Zásobník byste použili, pokud víte přesně, kolik dat musíte přidělit před časem kompilace a není příliš velký. Obvykle má maximální velikost již určenou při spuštění programu. Halda: Uloženo v RAM počítače stejně jako zásobník. V C ++ musí být proměnné na haldě zničeny ručně a nikdy nespadnou z rozsahu. Data jsou uvolněna pomocí mazání, mazání [] nebo zdarma. Pomalejší alokace ve srovnání s proměnnými v zásobníku. Používá se na vyžádání k přidělení bloku dat pro použití programem. Může dojít k fragmentaci, když existuje mnoho alokací a deallokací. V C ++ nebo C budou data vytvořená na haldě ukazována ukazateli a přidělena novým nebo malloc. Může dojít k selhání přidělení, pokud je požadováno přidělení příliš velké vyrovnávací paměti. Haldu byste použili, pokud nevíte přesně, kolik dat budete za běhu potřebovat, nebo pokud potřebujete alokovat hodně dat. Odpovědný za úniky paměti. Příklad: int foo () { char * pBuffer; // <- zatím nic přiděleno (kromě samotného ukazatele, který je přidělen zde na zásobníku). bool b = true; // Přiděleno na zásobníku. pokud (b) { // Vytvořte 500 bajtů na zásobníku char buffer [500]; // Vytvořte 500 bajtů na haldě pBuffer = nový znak [500]; } // <- zde je uvolněna vyrovnávací paměť, pBuffer nikoli } // <--- oops, došlo k úniku paměti, měl jsem zavolat delete [] pBuffer; | Nejdůležitějším bodem je, že halda a zásobník jsou obecné výrazy pro způsoby, kterými lze alokovat paměť. Mohou být implementovány mnoha různými způsoby a podmínky se vztahují na základní pojmy. V hromadě položek se položky nacházejí na sobě v pořadí, ve kterém byly umístěny, a můžete odstranit pouze horní(bez převrácení celé věci). Jednoduchost zásobníku spočívá v tom, že nemusíte udržovat tabulku obsahující záznam každé sekce přidělené paměti; jediné informace o stavu, které potřebujete, je jediný ukazatel na konec zásobníku. Chcete-li přidělit a zrušit přidělení, stačí zvýšit a snížit ten jediný ukazatel. Poznámka: Zásobník může být někdy implementován tak, aby začínal v horní části paměti a rozšiřoval se dolů, místo aby rostl nahoru. V hromadě není žádný konkrétní způsob, jakým jsou položky umístěny. Můžete zasáhnout a odebrat položky v libovolném pořadí, protože neexistuje žádná jasná „horní“ položka. Alokace haldy vyžaduje udržování úplného záznamu o tom, co je alokována paměť a co ne, stejně jako údržba některých režií ke snížení fragmentace, hledání souvislých segmentů paměti dostatečně velkých, aby vyhovovaly požadované velikosti atd. Paměť lze kdykoli uvolnit a ponechat volné místo. Alokátor paměti někdy provede úkoly údržby, jako je defragmentace paměti přesunem přidělené paměti nebo sběr odpadků - identifikace za běhu, když paměť již není v rozsahu a uvolnění paměti. Tyto obrázky by měly docela dobře popsat dva způsoby alokace a uvolnění paměti v zásobníku a hromadě. Mňam! Do jaké míry jsou řízeny operačním systémem nebo jazykovým modulem runtime? Jak již bylo zmíněno, halda a zásobník jsou obecné výrazy a lze je implementovat mnoha způsoby. Počítačové programy obvykle mají zásobník nazývaný zásobník volání, který ukládá informace relevantní pro aktuální funkci, jako je například ukazatel na kteroukoli funkci, ze které byl volán, a jakékoli místní proměnné. Protože funkce volají jiné funkce a poté se vracejí, zásobník roste a zmenšuje se, aby uchovával informace z funkcí dále v zásobníku volání. Program nad ním ve skutečnosti nemá kontrolu za běhu; je to určeno programovacím jazykem, operačním systémem a dokonce i architekturou systému. Halda je obecný termín používaný pro jakoukoli paměť, která je alokována dynamicky a náhodně; tj. mimo provoz. Paměť je obvykle přidělena operačním systémem, přičemž aplikace volá funkce API, aby provedla toto přidělení. Při správě dynamicky přidělené paměti je vyžadována velká část režie, kterou obvykle zpracovává běhový kód použitého programovacího jazyka nebo prostředí. Jaký je jejich rozsah? Zásobník volání je tak nízkoúrovňový koncept, který se netýká „rozsahu“ ve smyslu programování. Pokud demontujete nějaký kód, uvidíte relativní odkazy ve stylu ukazatele na části zásobníku, ale pokud jde o jazyk vyšší úrovně, jazyk ukládá svá vlastní pravidla rozsahu. Jedním důležitým aspektem zásobníku je však to, že jakmile se funkce vrátí, vše, co je lokální k této funkci, se okamžitě uvolní ze zásobníku. Funguje to tak, jak byste očekávali, že bude fungovat vzhledem k tomu, jak fungují vaše programovací jazyky. V hromadě je také těžké definovat. Rozsah je cokoli vystavený operačním systémem, ale váš programovací jazyk pravděpodobně přidává svá pravidla o tom, co je „rozsah“ ve vaší aplikaci. Architektura procesoru a OS používají virtuální adresování, které procesor převádí na fyzické adresy a vyskytují se chyby stránek atd. Sledují, které stránky patří ke kterým aplikacím. S tím si ale nikdy nemusíte dělat starosti, protože pro alokaci a uvolnění paměti použijete libovolnou metodu, kterou používá váš programovací jazyk, a zkontrolujete chyby (pokud alokace / uvolnění z nějakého důvodu selže). Co určuje velikost každého z nich? Opět záleží na jazyce, kompilátoru, operačním systému a architektuře. Zásobník je obvykle předem přidělen, protože podle definice to musí být souvislá paměť. Překladač jazyků nebo operační systém určí jeho velikost. Do zásobníku neukládáte obrovské množství dat, takže bude dostatečně velký, aby nikdy nebyl plně využit, kromě případů nechtěné nekonečné rekurze (tedy „přetečení zásobníku“) nebo jiných neobvyklých programovacích rozhodnutí. Hromada je obecný termín pro cokoli, co lze dynamicky přidělit. Podle toho, jak se na to díváte, se neustále mění velikost. V moderních procesorech a operačních systémech je přesný způsob, jakým to funguje, velmi abstrahovaný, takže se obvykle nemusíte příliš starat o to, jak to funguje hluboko, kromě toho (v jazycích, kde vám to umožňuje), nesmíte používat paměť dosud jste nepřidělili ani paměť, kterou jste uvolnili. Co dělá člověka rychlejším? Zásobník je rychlejší, protože veškerá volná paměť vždy sousedí. Není třeba udržovat seznam všech segmentů volné paměti, stačí jediný ukazatel na aktuální vrchol zásobníku. Kompilátoři obvykle pro tento účel ukládají tento ukazatel do speciálního rychlého registru. A co víc, následné operace na zásobníku jsou obvykle soustředěny do velmi blízkých oblastí paměti, což je na velmi nízké úrovni dobré pro optimalizaci procesorem na kostcemezipaměti. | (Tuto odpověď jsem přesunul z jiné otázky, která byla víceméně dupe na tuto otázku.) Odpověď na vaši otázku je specifická pro implementaci a může se u různých kompilátorů a architektur procesorů lišit. Zde je však zjednodušené vysvětlení. Zásobník i halda jsou oblasti paměti přidělené ze základního operačního systému (často virtuální paměť namapovaná na fyzickou paměť na vyžádání). V prostředí s více vlákny bude mít každé vlákno svůj vlastní zcela nezávislý zásobník, ale budou sdílet hromadu. Souběžný přístup musí být řízen na haldě a není možný na zásobníku. Halda Halda obsahuje propojený seznam použitých a volných bloků. Nové alokace na haldě (nové nebo malloc) jsou uspokojeny vytvořením vhodného bloku z jednoho z volných bloků. To vyžaduje aktualizaci seznamu bloků na haldě. Tyto meta informace o blocích na haldě jsou také uloženy na haldě často na malé ploše těsně před každým blokem. Jak hromada roste, nové bloky se často přidělují od nižších adres k vyšším adresám. Haldu tedy můžete považovat za hromadu paměťových bloků, které se při přidělování paměti zvětšují. Pokud je halda pro přidělení příliš malá, lze velikost často zvýšit získáním více paměti ze základního operačního systému. Přidělení a uvolnění mnoha malých bloků může haldu opustit ve stavu, kdy je mezi použitými bloky spousta malých volných bloků. Žádost o přidělení velkého bloku může selhat, protože žádný z volných bloků není dostatečně velký, aby vyhověl požadavku na alokaci, přestože kombinovaná velikost volných bloků může být dostatečně velká. Tomu se říká fragmentace haldy. Když je použitý blok, který sousedí s volným blokem, uvolněn, může být nový volný blok sloučen se sousedním volným blokem, aby se vytvořil větší volný blok, který účinně sníží fragmentaci haldy. Zásobník Zásobník často pracuje v úzkém tandemu se speciálním registrem na CPU s názvem ukazatel zásobníku. Ukazatel zásobníku původně směřoval k horní části zásobníku (nejvyšší adresa v zásobníku). CPU má speciální pokyny pro vkládání hodnot do zásobníku a jejich vyskakování zpět ze zásobníku. Každý push ukládá hodnotu na aktuálním místě ukazatele zásobníku a snižuje ukazatel zásobníku. Pop načte hodnotu, na kterou ukazuje ukazatel zásobníku, a poté zvětší ukazatel zásobníku (nenechte se zmást tím, že přidání hodnoty do zásobníku sníží ukazatel zásobníku a odebráním hodnoty se zvýší. Pamatujte, že zásobník narůstá na dno). Uložené a načtené hodnoty jsou hodnoty registrů CPU. Když se funkce nazývá, CPU používá speciální instrukce, které tlačí aktuální ukazatel instrukce, tj. Adresu kódu prováděného na zásobníku. CPU poté přeskočí na funkci nastavením ukazatel instrukce na adresu volané funkce. Později, když se funkce vrátí, se starý ukazatel instrukce vyskočí ze zásobníku a spuštění se obnoví v kódu hned po volání funkce. Po zadání funkce se ukazatel zásobníku zmenší, aby se v zásobníku přidělovalo více místa pro místní (automatické) proměnné. Pokud má funkce jednu lokální 32bitovou proměnnou, jsou na zásobníku vyčleněny čtyři bajty. Když se funkce vrátí, ukazatel zásobníku se přesune zpět, aby uvolnil přidělenou oblast. Pokud má funkce parametry, tyto se před voláním funkce vloží do zásobníku. Kód ve funkci je poté schopen navigovat nahoru v zásobníku od aktuálního ukazatele zásobníku k vyhledání těchto hodnot. Volání funkce vnoření fungují jako kouzlo. Každé nové volání bude přidělovat parametry funkce, zpáteční adresu a prostor pro místní proměnné a tyto záznamy o aktivaci lze skládat pro vnořená volání a po návratu funkcí se uvolní správným způsobem. Protože zásobník je omezený blok paměti, můžete způsobit přetečení zásobníku voláním příliš mnoha vnořených funkcí a / nebo přidělením příliš velkého prostoru pro místní proměnné. Paměťová oblast použitá pro zásobník je často nastavena takovým způsobem, že zápis pod spodní (nejnižší adresu) zásobníku způsobí v CPU depeši nebo výjimku. Tuto výjimečnou podmínku pak může modul runtime zachytit a převést na nějakou výjimku přetečení zásobníku. Může být funkce přidělena na haldě místo zásobníku? Ne, aktivační záznamy pro funkce (tj. Místní nebo automatické proměnné) jsou přiděleny na zásobníku, který se používá nejen k ukládání těchto proměnných, ale také ke sledování volání vnořených funkcí. Jak je halda spravována, je opravdu na běhovém prostředí. C používá malloc a C ++ používá nové, ale mnoho dalších jazyků má odvoz odpadu. Zásobník je však funkce nižší úrovně úzce spojená s architekturou procesoru. Pěstování hromady, když není dostatek místa, není od té doby příliš těžkélze jej implementovat do volání knihovny, které zpracovává haldu. Pěstování zásobníku je však často nemožné, protože přetečení zásobníku se zjistí, až když je příliš pozdě; a vypnutí vlákna provádění je jedinou schůdnou možností. | V následujícím C # kódu public void Metoda1 () { int i = 4; int y = 2; třída1 cls1 = nová třída1 (); } Zde je popsáno, jak je spravována paměť Místní proměnné, které musí trvat tak dlouho, dokud je vyvolání funkce v zásobníku. Hromada se používá pro proměnné, jejichž životnost ve skutečnosti předem neznáme, ale očekáváme, že chvíli vydrží. Ve většině jazyků je důležité, abychom v době kompilace věděli, jak velká je proměnná, pokud ji chceme uložit do zásobníku. Objekty (jejichž velikost se při aktualizaci liší) jdou na hromadu, protože v době vytváření nevíme, jak dlouho vydrží. V mnoha jazycích se hromada shromažďuje za účelem nalezení objektů (například objektu cls1), které již nemají žádné odkazy. V Javě jde většina objektů přímo do hromady. V jazycích, jako je C / C ++, mohou struktury a třídy často zůstat v zásobníku, pokud nemáte co do činění s ukazateli. Více informací naleznete zde: Rozdíl mezi alokací paměti zásobníku a haldy «timmurphy.org a tady: Vytváření objektů na zásobníku a hromadě Tento článek je zdrojem výše uvedeného obrázku: Šest důležitých konceptů .NET: Zásobník, halda, typy hodnot, referenční typy, box a unboxing - CodeProject mějte však na paměti, že může obsahovat určité nepřesnosti. | The Stack Když zavoláte funkci, argumenty této funkce plus nějaká další režie se vloží do zásobníku. Jsou zde také uloženy některé informace (například kam jít po návratu). Když deklarujete proměnnou uvnitř své funkce, je tato proměnná přidělena také na zásobníku. Přidělení zásobníku je docela jednoduché, protože vždy uvolňujete peníze v opačném pořadí, ve kterém přidělujete. Zásobník se přidává při zadávání funkcí, příslušná data se odstraňují při jejich ukončení. To znamená, že máte tendenci zůstat v malé oblasti zásobníku, pokud nezavoláte spoustu funkcí, které volají spoustu dalších funkcí (nebo nevytvoříte rekurzivní řešení). Halda Hromada je obecný název místa, kam vkládáte data, která vytvoříte za běhu. Pokud nevíte, kolik vesmírných lodí váš program vytvoří, je pravděpodobné, že k vytvoření každé vesmírné lodi použijete nový (nebo malloc nebo ekvivalentní) operátor. Tato alokace bude chvíli trvat, takže je pravděpodobné, že uvolníme věci v jiném pořadí, než jsme je vytvořili. Hromada je tedy mnohem složitější, protože tam nakonec budou oblasti paměti, které se nepoužívají, prokládané s bloky, které jsou - paměť se fragmentuje. Najít volnou paměť potřebné velikosti je obtížný problém. Proto je třeba se haldě vyhnout (i když se stále často používá). Implementace Implementace zásobníku i haldy je obvykle na runtime / OS. Hry a další aplikace, které jsou kritické z hlediska výkonu, často vytvářejí svá vlastní řešení paměti, která z hromady popadnou velkou část paměti a poté ji interně připraví, aby se nemusely spoléhat na operační systém. To je praktické pouze v případě, že se využití vaší paměti zcela liší od normy - tj. U her, kde načtete úroveň v jedné obrovské operaci a můžete ji v jiné obrovské operaci zahodit. Fyzické umístění v paměti To je méně relevantní, než si myslíte kvůli technologii zvané virtuální paměť, díky níž si váš program myslí, že máte přístup na určitou adresu, kde jsou fyzická data někde jinde (dokonce i na pevném disku!). Adresy, které pro zásobník získáte, jsou v rostoucím pořadí, jak se váš strom volání prohlubuje. Adresy haldy jsou nepředvídatelné (tj. Specifické pro implementaci) a upřímně řečeno nejsou důležité. | Aby bylo jasno, tato odpověď obsahuje nesprávné informace (thomas opravil svou odpověď po komentářích, super :)). Ostatní odpovědi se jen vyhýbají vysvětlování, co znamená statická alokace. Vysvětlím tedy tři hlavní formy přidělování a to, jak obvykle souvisejí s hromadou, zásobníkem a datovým segmentem níže. Ukážu také několik příkladů v C / C ++ a Pythonu, které lidem pomohou porozumět. "Statické" (staticky přidělené) proměnné nejsou na zásobníku přiděleny. Nepředpokládejte to - mnoho lidí to dělá jen proto, že „statický“ zní hodně jako „zásobník“. Ve skutečnosti neexistují ani v zásobníku, ani v hromadě. Jsou součástí tzv. Datového segmentu. Obecně je však lepší zvážit „rozsah“ a „životnost“ než „zásobník“ a „hromadu“. Rozsah odkazuje na to, jaké části kódu mohou přistupovat k proměnné. Obecně si myslíme, že místní rozsah (lze získat přístup pouze aktuální funkcí) versus globální rozsah (lze přistupovat kdekoli), ačkoli rozsah může být mnohem složitější. Celoživotní označuje, když je proměnná přidělena a uvolněna během provádění programu. Obvykle uvažujeme o statické alokaci (proměnnábude přetrvávat po celou dobu trvání programu, což je užitečné pro ukládání stejných informací napříč několika voláními funkcí) oproti automatickému přidělení (proměnná přetrvává pouze během jednoho volání funkce, což je užitečné pro ukládání informací, které se používají pouze během funkce a lze ji zahodit, až budete hotovi) versus dynamická alokace (proměnné, jejichž trvání je definováno za běhu, namísto času kompilace jako statické nebo automatické). Ačkoli většina překladačů a tlumočníků implementuje toto chování podobně, pokud jde o používání komínů, hromad, atd., Kompilátor může někdy tyto konvence porušit, pokud chce, pokud je chování správné. Například kvůli optimalizaci může místní proměnná existovat pouze v registru nebo může být zcela odstraněna, i když většina místních proměnných existuje v zásobníku. Jak již bylo uvedeno v několika komentářích, máte možnost implementovat kompilátor, který nepoužívá ani hromádku, ani hromadu, ale místo toho některé další mechanismy úložiště (zřídka se to děje, protože hromádky a hromady jsou pro to skvělé). K ilustraci toho všeho poskytnu nějaký jednoduchý anotovaný C kód. Nejlepší způsob, jak se to naučit, je spustit program pod debuggerem a sledovat jeho chování. Pokud dáváte přednost čtení pythonu, přejděte na konec odpovědi :) // Staticky přiděleno v datovém segmentu při prvním načtení programu / DLL // Zrušení přidělení při ukončení programu / DLL // scope - lze přistupovat odkudkoli v kódu int someGlobalVariable; // Staticky přiděleno v datovém segmentu při prvním načtení programu // Přiděleno při ukončení programu / DLL // scope - lze přistupovat odkudkoli v tomto konkrétním souboru kódu static int someStaticVariable; // "someArgument" je přidělen na zásobník pokaždé, když je volána funkce MyFunction // "someArgument" je uvolněn, když se MyFunction vrátí // obor - je přístupný pouze v rámci MyFunction () void MyFunction (int someArgument) { // Staticky přiděleno v datovém segmentu při prvním načtení programu // Přiděleno při ukončení programu / DLL // obor - je přístupný pouze v rámci MyFunction () static int someLocalStaticVariable; // Přiděleno v zásobníku pokaždé, když je volána funkce MyFunction // Deallocated when MyFunction returns // obor - lze přistupovat pouze v rámci MyFunction () int someLocalVariable; // Při každém vyvolání funkce MyFunction je na zásobníku přidělen * ukazatel * // Tento ukazatel je uvolněn, když se MyFunction vrátí // obor - ukazatel je přístupný pouze v rámci MyFunction () int * someDynamicVariable; // Tento řádek způsobí, že bude na haldě přiděleno celé číslo // když je tento řádek proveden. Všimněte si, že to není na začátku roku // volání funkce MyFunction (), jako jsou automatické proměnné // obor - do tohoto prostoru má přístup pouze kód v rámci MyFunction () // * prostřednictvím této konkrétní proměnné *. // Pokud však předáte adresu někam jinam, tento kód // má také přístup someDynamicVariable = new int; // Tento řádek uvolní místo pro celé číslo v haldě. // Pokud bychom to nenapsali, došlo by k „úniku paměti“. // Všimněte si zásadního rozdílu mezi zásobníkem a hromadou // halda musí být spravována. Zásobník je spravován za nás. odstranit someDynamicVariable; // V ostatních případech místo uvolnění tohoto haldy místo vy // může uložit adresu někde trvaleji a použít ji později. // Některé jazyky se o vás dokonce postarají ... ale // vždy je potřeba se o to postarat za běhu nějakým mechanismem. // Když se funkce vrátí, someArgument, someLocalVariable // a ukazatel someDynamicVariable jsou uvolněny. // Prostor, na který ukazuje someDynamicVariable, již byl // uvolněno před návratem. vrátit se; } // Všimněte si, že someGlobalVariable, someStaticVariable a // someLocalStaticVariable nadále existují a nejsou // uvolněno až do ukončení programu. Obzvláště uštěpačný příklad, proč je důležité rozlišovat mezi životností a rozsahem, je to, že proměnná může mít místní rozsah, ale statickou životnost - například „someLocalStaticVariable“ v ukázce kódu výše. Díky těmto proměnným mohou být naše běžné, ale neformální návyky pojmenování velmi matoucí. Například když říkáme „místní“, máme obvykle na mysli „automaticky přidělená proměnná s lokálním rozsahem“ a když říkáme globální, máme obvykle na mysli „staticky přidělenou proměnnou s globálním rozsahem“. Bohužel, pokud jde o věci jako „soubor se staticky přidělenými proměnnými“, mnoho lidí řekne ... „jo ???“. Některé možnosti syntaxe v C / C ++ tento problém zhoršují - například mnoho lidí si myslí, že globální proměnné nejsou „statické“ kvůli níže uvedené syntaxi. int var1; // Má globální rozsah a statickou alokaci static int var2; // Má rozsah souboru a statickou alokaci int main () {návrat 0;} Všimněte si, že vložení klíčového slova „static“ do výše uvedené deklarace zabrání var2 v globálním rozsahu. Globální var1 má statickou alokaci. To neníintuitivní! Z tohoto důvodu se snažím při popisu oboru nikdy nepoužívat slovo „statický“ a místo toho říkám něco jako „soubor“ nebo „omezený soubor“. Mnoho lidí však používá výraz „statický“ nebo „statický rozsah“ k popisu proměnné, ke které lze přistupovat pouze z jednoho souboru kódu. V kontextu životnosti „statický“ vždy znamená, že proměnná je přidělena na začátku programu a uvolněna při ukončení programu. Někteří lidé si myslí, že tyto koncepty jsou specifické pro C / C ++. Nejsou. Například ukázka Pythonu níže ilustruje všechny tři typy alokace (v interpretovaných jazycích jsou možné jemné rozdíly, do kterých se sem nedostanu). z datetime import datetime třída Zvíře: _FavoriteFood = 'Nedefinováno' # _FavoriteFood je staticky přiděleno def PetAnimal (self): curTime = datetime.time (datetime.now ()) # curTime je automaticky přidělen print ("Děkuji, že jste mě hladili. Ale je to" + str (curTime) + ", měli byste mě nakrmit. Moje oblíbené jídlo je" + self._FavoriteFood ") třída Kočka (zvíře): _FavoriteFood = 'tuna' # Poznámka, protože přepíšeme, třída Cat má svou vlastní staticky přidělenou proměnnou _FavoriteFood, odlišnou od Animal třída pes (zvíře): _FavoriteFood = 'steak' # Podobně třída Dog dostane svou vlastní statickou proměnnou. Je důležité si uvědomit - tato statická proměnná je sdílena mezi všemi instancemi Dog, proto není dynamická! pokud __name__ == "__main__": vousy = Cat () # dynamicky přiděleno fido = Dog () # Dynamicky přiděleno rinTinTin = Dog () # Dynamicky přiděleno vousy.PetAnimal () fido.PetAnimal () rinTinTin.PetAnimal () Dog._FavoriteFood = 'mléčné kosti' vousy.PetAnimal () fido.PetAnimal () rinTinTin.PetAnimal () # Výstup je: # Děkuji, že jste mě pohladili. Ale je 13: 05: 02.255000, měli byste mě nakrmit. Moje oblíbené jídlo je tuňák # Děkuji, že jste mě pohladili. Ale je 13: 05: 02.255000, měli byste mě nakrmit. Moje oblíbené jídlo je steak # Děkuji, že jste mě pohladili. Ale je 13: 05: 02.255000, měli byste mě nakrmit. Moje oblíbené jídlo je steak # Děkuji, že jste mě pohladili. Ale je 13: 05: 02.255000, měli byste mě nakrmit. Moje oblíbené jídlo je tuňák # Děkuji, že jste mě pohladili. Ale je 13: 05: 02.255000, měli byste mě nakrmit. Moje oblíbené jídlo jsou mléčné kosti # Děkuji, že jste mě pohladili. Ale je 13: 05: 02.256000, měli byste mě nakrmit. Moje oblíbené jídlo jsou mléčné kosti | Jiní docela dobře odpověděli na široké tahy, takže uvedu několik podrobností. Zásobník a hromada nemusí být singulární. Běžnou situací, ve které máte více než jeden zásobník, je situace, kdy máte v procesu více než jedno vlákno. V tomto případě má každé vlákno svůj vlastní zásobník. Můžete také mít více než jednu hromadu, například některé konfigurace DLL mohou mít za následek přidělení různých knihoven DLL z různých hromad, což je důvod, proč je obecně špatný nápad uvolnit paměť přidělenou jinou knihovnou. V C můžete získat výhodu přidělení proměnné délky pomocí použití alloca, který přiděluje na zásobníku, na rozdíl od alloc, který přiděluje na haldě. Tato paměť nepřežije váš návratový příkaz, ale je užitečná pro vyrovnávací paměť. Vytvoření obrovské dočasné vyrovnávací paměti v systému Windows, kterou příliš nepoužíváte, není zdarma. Je to proto, že kompilátor vygeneruje smyčku sondy zásobníku, která je volána pokaždé, když je zadána vaše funkce, aby se ujistil, že zásobník existuje (protože Windows používá jednu ochrannou stránku na konci vašeho zásobníku, aby zjistil, kdy je potřeba zásobník zvětšit. Pokud přistupujete do paměti více než jedné stránky z konce zásobníku, dojde k chybě). Příklad: void myfunction () { char velký [10 000 000]; // Udělejte něco, co se používá pouze pro prvních 1 kB velkých 99% času. } | Jiní přímo odpověděli na vaši otázku, ale když se pokoušíte porozumět zásobníku a hromadě, myslím, že je užitečné zvážit rozložení paměti tradičního procesu UNIX (bez vláken a alokátorů založených na mmap ()). Na webové stránce Glosář správy paměti je schéma tohoto rozložení paměti. Zásobník a halda jsou tradičně umístěny na opačných koncích virtuálního adresního prostoru procesu. Zásobník se při přístupu automaticky zvětší až na velikost nastavenou jádrem (kterou lze upravit pomocí setrlimit (RLIMIT_STACK, ...)). Halda roste, když alokátor paměti vyvolá systémové volání brk () nebo sbrk () a mapuje více stránek fyzické paměti do virtuálního adresního prostoru procesu. V systémech bez virtuální paměti, jako jsou některé vestavěné systémy, často platí stejné základní rozložení, kromě velikosti zásobníku a haldy. V jiných vestavěných systémech (jako jsou systémy založené na mikroprocesorech Microchip PIC) je však programový zásobník samostatným blokem paměti, který není adresovatelný podle pokynů pro pohyb dat, a lze jej upravovat nebo číst pouze nepřímo pomocí pokynů k toku programu (volání, návrat atd.). Jiné architektury, například procesory Intel Itanium, mají více zásobníků. V tomto smyslu je zásobník prvkem architektury CPU. | Zásobník je částpaměti, kterou lze manipulovat pomocí několika jazykových instrukcí pro sestavení klíčů, jako například „pop“ (odebrání a vrácení hodnoty ze zásobníku) a „push“ (vložení hodnoty do zásobníku), ale také volání (volání podprogramu - posune adresu, aby se vrátila do zásobníku) a vrátit se (návrat z podprogramu - toto vyskočí adresu ze zásobníku a skočí na ni). Je to oblast paměti pod registrem ukazatele zásobníku, kterou lze nastavit podle potřeby. Zásobník se také používá k předávání argumentů do podprogramů a také k uchování hodnot v registrech před voláním podprogramů. Hromada je část paměti, která je dána aplikaci operačním systémem, obvykle prostřednictvím systému Syscall, jako je malloc. V moderních operačních systémech je tato paměť sada stránek, ke kterým má přístup pouze volající proces. Velikost zásobníku se určuje za běhu a po spuštění programu se obecně nezvětší. V programu C musí být zásobník dostatečně velký, aby pojal každou proměnnou deklarovanou v každé funkci. Halda podle potřeby dynamicky poroste, ale operační systém nakonec zavolá (často naroste haldu o více, než je hodnota požadovaná malloc, takže alespoň někteří budoucí mallokové se nebudou muset vrátit zpět do získat více paměti. Toto chování je často přizpůsobitelné) Protože jste přidělili stoh před spuštěním programu, nikdy nemusíte malloc před použitím stohovat, takže to je malá výhoda. V praxi je velmi těžké předvídat, co bude rychlé a co pomalé v moderních operačních systémech, které mají subsystémy virtuální paměti, protože to, jak jsou stránky implementovány a kde jsou uloženy, je detail implementace. | Co je to stack? Stoh je hromada objektů, obvykle ten, který je přehledně uspořádán. Zásobníky ve výpočetních architekturách jsou oblasti paměti, kde jsou data přidávána nebo odebírána způsobem last-in-first-out. Ve vícevláknové aplikaci bude mít každé vlákno svůj vlastní zásobník. Co je halda? Hromada je neuspořádaná sbírka věcí nashromážděných nahodile. Ve výpočetních architekturách je halda oblast dynamicky přidělené paměti, která je automaticky spravována operačním systémem nebo knihovnou správce paměti. Paměť na haldě se během provádění programu pravidelně přiděluje, uvolňuje a mění její velikost, což může vést k problému nazývanému fragmentace. K fragmentaci dochází, když jsou objekty paměti přiděleny s malými mezerami mezi nimi, které jsou příliš malé na to, aby pojaly další objekty paměti. Čistým výsledkem je procento prostoru haldy, které nelze použít pro další přidělení paměti. Oba společně Ve vícevláknové aplikaci bude mít každé vlákno svůj vlastní zásobník. Ale všechna různá vlákna budou sdílet hromadu. Protože různá vlákna sdílejí haldu ve vícevláknové aplikaci, znamená to také, že musí existovat určitá koordinace mezi vlákny, aby se nepokoušely přistupovat ke stejné části paměti v haldě a manipulovat s ní stejný čas. Co je rychlejší - hromádka nebo hromada? A proč? Zásobník je mnohem rychlejší než halda. Důvodem je způsob, jakým je v zásobníku alokována paměť. Přidělení paměti na zásobníku je stejně jednoduché jako přesunutí ukazatele zásobníku nahoru. Pro lidi, kteří začínají s programováním, je pravděpodobně dobrý nápad použít zásobník, protože je to jednodušší. Protože je zásobník malý, měli byste ho použít, když přesně víte, kolik paměti budete pro svá data potřebovat, nebo pokud víte, že velikost vašich dat je velmi malá. Je lepší použít haldu, když víte, že pro svá data budete potřebovat hodně paměti, nebo si jen nejste jisti, kolik paměti budete potřebovat (jako u dynamického pole). Model paměti Java Zásobník je oblast paměti, kde jsou uloženy místní proměnné (včetně parametrů metody). Pokud jde o objektové proměnné, jedná se pouze o odkazy (ukazatele) na skutečné objekty na haldě. Pokaždé, když dojde k vytvoření instance objektu, je část haldy paměti vyčleněna k uložení dat (stavu) daného objektu. Protože objekty mohou obsahovat další objekty, mohou některá z těchto dat ve skutečnosti obsahovat odkazy na tyto vnořené objekty. | Myslím, že mnoho dalších lidí vám dalo v této věci většinou správné odpovědi. Jeden detail, který však chyběl, je, že „halda“ by měla být ve skutečnosti pravděpodobně nazývána „obchodem zdarma“. Důvodem tohoto rozdílu je, že původní bezplatný obchod byl implementován s datovou strukturou známou jako „binomická halda“. Z tohoto důvodu byla alokace z raných implementací malloc () / free () alokace z hromady. V této moderní době je však většina bezplatných obchodů implementována s velmi propracovanými datovými strukturami, které nejsou binomickými hromadami. | Se zásobníkem můžete dělat několik zajímavých věcí. Například máte funkce jako alloca (za předpokladu, že můžete překonat bohatá varování týkající se jejího použití), což je forma malloc, kterákonkrétně používá zásobník, ne hromadu, pro paměť. To znamená, že chyby paměti založené na zásobníku jsou jedny z nejhorších, jaké jsem kdy zažil. Pokud použijete haldy paměti a překročíte hranice svého přiděleného bloku, máte slušnou šanci spustit poruchu segmentu. (Ne 100%: váš blok může být náhodně sousedící s jiným, který jste dříve přidělili.) Ale protože proměnné vytvořené v zásobníku jsou vždy vzájemně sousedící, zápis mimo hranice může změnit hodnotu jiné proměnné. Naučil jsem se, že kdykoli mám pocit, že můj program přestal dodržovat logické zákony, pravděpodobně jde o přetečení vyrovnávací paměti. | Jednoduše je to zásobník, kde se vytvářejí místní proměnné. Také pokaždé, když zavoláte podprogram, počítadlo programu (ukazatel na další strojovou instrukci) a všechny důležité registry a někdy se parametry dostanou do zásobníku. Pak se všechny lokální proměnné uvnitř podprogramu vloží do zásobníku (a použijí se odtud). Když podprogram skončí, všechny tyto věci se vyskočí zpět ze zásobníku. Počítač a data registru se dostanou a vrátí tam, kde byla, jak jsou vysunuta, takže váš program může pokračovat veselou cestou. Halda je oblast paměti, z níž jsou přidělována dynamická přidělení paměti (explicitní volání „nové“ nebo „přidělení“). Jedná se o speciální datovou strukturu, která dokáže sledovat bloky paměti různých velikostí a jejich stav alokace. V „klasických“ systémech byla RAM vyložena tak, že ukazatel zásobníku začínal v dolní části paměti, ukazatel haldy začal v horní části a rostly směrem k sobě. Pokud se překrývají, máte nedostatek paměti RAM. To však nefunguje s moderními operačními systémy s více vlákny. Každé vlákno musí mít svůj vlastní zásobník a ty se mohou vytvářet dynamicky. | Z WikiAnwser. Zásobník Když funkce nebo metoda volá jinou funkci, která zase volá jinou funkci atd., Provádění všech těchto funkcí zůstane pozastaveno, dokud poslední funkce nevrátí svou hodnotu. Tento řetězec pozastavených volání funkcí je zásobník, protože prvky v zásobníku (volání funkcí) závisí na sobě navzájem. Zásobník je důležité vzít v úvahu při zpracování výjimek a provádění podprocesů. Halda Halda je jednoduše paměť, kterou programy používají k ukládání proměnných. Prvek hromady (proměnné) nemají žádné vzájemné závislosti a lze k nim kdykoli přistupovat náhodně. | Zásobník Velmi rychlý přístup Nemusíte explicitně de-alokovat proměnné Prostor je efektivně spravován CPU, paměť nebude fragmentována Pouze místní proměnné Omezení velikosti zásobníku (v závislosti na operačním systému) Velikost proměnných nelze změnit Halda K proměnným lze přistupovat globálně Žádné omezení velikosti paměti (Relativně) pomalejší přístup Žádné zaručené efektivní využití prostoru, paměť se může časem fragmentovat, protože bloky paměti jsou přiděleny a poté uvolněny Musíte spravovat paměť (máte na starosti přidělování a uvolňování proměnných) Proměnné lze měnit pomocí realloc () | Ve zkratce Zásobník se používá pro přidělení statické paměti a hromada pro dynamické přidělení paměti, obě uložené v paměti RAM počítače. Podrobně The Stack Zásobník je datová struktura „LIFO“ (poslední dovnitř, první ven), která je poměrně pečlivě spravována a optimalizována CPU. Pokaždé, když funkce deklaruje novou proměnnou, je „vložena“ do zásobníku. Pak pokaždé, když funkce skončí, všechny proměnné vložené do zásobníku touto funkcí se uvolní (to znamená, že se smažou). Jakmile je proměnná zásobníku uvolněna, tato oblast paměti bude k dispozici pro další proměnné zásobníku. Výhodou použití zásobníku k ukládání proměnných je to, že paměť je spravována za vás. Nemusíte přidělovat paměť ručně, nebo ji uvolnit, jakmile ji již nepotřebujete. A co víc, protože CPU organizuje paměť zásobníku tak efektivně, čtení a zápis do proměnných zásobníku je velmi rychlé. Více najdete zde. Halda Halda je oblast paměti vašeho počítače, která pro vás není spravována automaticky a není tak pevně spravována CPU. Je to volně plovoucí oblast paměti (a je větší). Chcete-li alokovat paměť na haldě, musíte použít malloc () nebo calloc (), což jsou integrované funkce C. Jakmile přidělíte paměť na haldě, jste zodpovědní za použití free () k uvolnění této paměti, jakmile ji již nepotřebujete. Pokud to neuděláte, bude mít váš program tzv. Nevracení paměti. To znamená, že paměť na haldě bude stále odložena stranou (a nebude k dispozici jiným procesům). Jak uvidíme v sekci ladění, existuje nástroj s názvem Valgrind, který vám pomůže detekovat úniky paměti. Na rozdíl od zásobníku nemá halda omezení velikosti proměnné velikosti (kromě zjevných fyzických omezení vašeho počítače). Halda paměti je o něco pomalejší, aby se dalo číst a zapisovat do ní, protože pro přístup k paměti na haldě je nutné použít ukazatele. Brzy si povíme o ukazatelích. Na rozdíl od zásobníkuproměnné vytvořené na haldě jsou přístupné libovolné funkci kdekoli ve vašem programu. Hromadné proměnné mají v podstatě globální rozsah. Více najdete zde. Proměnné přidělené na zásobníku se ukládají přímo do paměti a přístup do této paměti je velmi rychlý a jeho přidělení je řešeno při kompilaci programu. Když funkce nebo metoda volá jinou funkci, která zase volá jinou funkci atd., Provádění všech těchto funkcí zůstane pozastaveno, dokud poslední funkce nevrátí svou hodnotu. Zásobník je vždy rezervován v pořadí LIFO, naposledy rezervovaný blok je vždy dalším blokem, který má být uvolněn. Díky tomu je sledování zásobníku opravdu jednoduché, uvolnění bloku ze zásobníku není nic jiného než úprava jednoho ukazatele. Proměnné přidělené na haldě mají svoji paměť přidělenou za běhu a přístup k této paměti je o něco pomalejší, ale velikost haldy je omezena pouze velikostí virtuální paměti. Prvky haldy nemají mezi sebou žádné závislosti a lze k nim kdykoli kdykoli přistupovat náhodně. Blok můžete kdykoli přidělit a kdykoli uvolnit. Díky tomu je mnohem složitější sledovat, které části haldy jsou v daném okamžiku přiděleny nebo volné. Zásobník můžete použít, pokud víte přesně, kolik dat je třeba přidělit před časem kompilace, a není příliš velký. Haldu můžete použít, pokud nevíte přesně, kolik dat budete za běhu potřebovat, nebo pokud potřebujete alokovat hodně dat. V situaci s více vlákny bude mít každé vlákno svůj vlastní zcela nezávislý zásobník, ale budou sdílet hromadu. Zásobník je specifický pro vlákno a hromada je specifická pro aplikaci. Zásobník je důležité vzít v úvahu při zpracování výjimek a provádění podprocesů. Každé vlákno získá zásobník, zatímco pro aplikaci je obvykle jen jedna hromada (i když není neobvyklé mít několik hromád pro různé typy přidělení). Za běhu, pokud aplikace potřebuje více haldy, může přidělit paměť z volné paměti a pokud zásobník potřebuje paměť, může přidělit paměť z volné paměti přidělené paměti pro aplikaci. Dokonce i zde je uvedeno více podrobností. Nyní přejděte k odpovědím na vaši otázku. Do jaké míry jsou řízeny operačním systémem nebo jazykovým modulem runtime? Když je vlákno vytvořeno, OS přiděluje zásobník pro každé vlákno na úrovni systému. Obvykle se operační systém volá jazykovým modulem runtime, aby přidělil haldu pro aplikaci. Více najdete zde. Jaký je jejich rozsah? Již uvedeno nahoře. "Zásobník můžete použít, pokud přesně víte, kolik dat musíte alokovat před časem kompilace a není příliš velký. Haldu můžete použít, pokud nevíte přesně, kolik dat budete za běhu potřebovat, nebo jestli musíte přidělit hodně dat. “ Více najdete zde. Co určuje velikost každého z nich? Velikost zásobníku je nastavena operačním systémem při vytvoření vlákna. Velikost haldy je nastavena při spuštění aplikace, ale může se zvětšovat podle potřeby místa (alokátor požaduje více paměti z operačního systému). Co dělá člověka rychlejším? Alokace zásobníku je mnohem rychlejší, protože vše, co opravdu dělá, je pohyb ukazatele zásobníku. Pomocí fondů paměti můžete z alokace haldy získat srovnatelný výkon, ale to přichází s mírnou přidanou složitostí a vlastními bolestmi hlavy. Stack vs. halda také není jen úvahou o výkonu; také vám řekne hodně o očekávané životnosti objektů. Podrobnosti naleznete zde. | Dobře, jednoduše a stručně řečeno, znamenají objednané a neobjednané ...! Zásobník: V položkách zásobníku se věci navzájem překrývají, což znamená, že budou zpracovány rychleji a efektivněji! ... Takže vždy existuje index, který nasměruje konkrétní položku, a také zpracování bude rychlejší, mezi položkami také existuje vztah! ... Halda: Žádná objednávka, zpracování bude pomalejší a hodnoty budou pokazeny společně s žádnou konkrétní objednávkou nebo indexem ... jsou náhodné a neexistuje mezi nimi žádný vztah ... takže provedení a doba používání se mohou lišit ... Také vytvořím obrázek níže, abych ukázal, jak mohou vypadat: | zásobník, halda a data každého procesu ve virtuální paměti: | V 80. letech se UNIX šířil jako králíci s velkými společnostmi, které si vyráběly vlastní. Exxon měl jeden stejně jako desítky značek ztracených v historii. O tom, jak je rozložena paměť, rozhodlo mnoho implementátorů. Typický program C byl rozložen do paměti s příležitost ke zvýšení změnou hodnoty brk (). Typicky bylo HEAP těsně pod touto hraniční hodnotou a zvýšení brk zvýšilo množství dostupné hromady. Jediný STACK byl obvykle oblast pod HEAP, která byla traktem paměti neobsahující nic hodnotného až do horní části dalšího pevného bloku paměti. Tento další blok byl často CODE, který mohl být přepsán daty zásobníku v jednom ze slavných hacků své doby. Jeden typický paměťový blok byl BSS (blok nulahodnoty) který nebyl náhodně vynulován v nabídce jednoho výrobce. Další byla DATA obsahující inicializované hodnoty, včetně řetězců a čísel. Třetí byl CODE obsahující CRT (C runtime), hlavní, funkce a knihovny. Nástup virtuální paměti v systému UNIX mění řadu omezení. Neexistuje žádný objektivní důvod, proč musí být tyto bloky souvislé, nebo pevnou velikostí nebo objednaným konkrétním způsobem hned. Samozřejmě, před UNIXem byla Multics, která těmito omezeními netrpěla. Zde je schéma ukazující jedno z rozložení paměti té doby. | Pár centů: Myslím, že bude dobré nakreslit paměť graficky a jednodušeji: Šipky - ukazují, kde růst zásobníku a haldy, velikost zásobníku procesu mají limit, definované v OS, limity velikosti zásobníku vláken podle parametrů ve vlákně vytvářejí API obvykle. Halda obvykle omezuje maximální velikost virtuální paměti procesu, například pro 32 bitů 2 až 4 GB. Tak jednoduchý způsob: halda procesu je obecná pro proces a všechna vlákna uvnitř, používá se pro přidělení paměti v běžném případě s něčím jako malloc (). Zásobník je rychlá paměť pro uložení běžných ukazatelů a proměnných funkce návratu, zpracovaných jako parametry při volání funkce, lokální funkční proměnné. | Jelikož některé odpovědi šlapaly, přispěju svým roztočem. Překvapivě nikdo nezmínil, že více (tj. Nesouvisí s počtem běžících vláken na úrovni OS) se hromádky volání nacházejí nejen v exotických jazycích (PostScript) nebo na platformách (Intel Itanium), ale také ve vláknech, zelených vláknech a některé implementace korutin. Vlákna, zelená vlákna a korutiny jsou v mnoha ohledech podobné, což vede k velkému zmatku. Rozdíl mezi vlákny a zelenými nitěmi je v tom, že první používají kooperativní multitasking, zatímco druhý může obsahovat buď kooperativní, nebo preventivní (nebo dokonce obojí). Rozdíl mezi vlákny a korutiny naleznete zde. V každém případě je účelem obou vláken, zelených vláken a coututin mít více funkcí provádějících současně, ale ne paralelně (viz tuto otázku SO pro rozlišení) v rámci jednoho vlákna na úrovni OS, přenášející kontrolu tam a zpět od sebe navzájem organizovaně. Při použití vláken, zelených vláken nebo korutinů máte obvykle pro každou funkci samostatný zásobník. (Technicky je to nejen zásobník, ale celý kontext provádění je na funkci. A co je nejdůležitější, CPU se registruje.) Pro každé vlákno existuje tolik zásobníků, kolik je současně spuštěných funkcí, a vlákno přepíná mezi prováděním každé funkce podle logiky vašeho programu. Když funkce běží na svém konci, její zásobník je zničen. Takže počet a životnost zásobníků jsou dynamické a nejsou určeny počtem podprocesů na úrovni OS! Všimněte si, že jsem řekl „obvykle mají samostatný zásobník na funkci“. Existují jak hromádkové, tak bezhromadné implementace kurzů. Nejpozoruhodnější stohovatelné implementace C ++ jsou Boost.Coroutine a asynchronní / čekající Microsoft PPL. (Nicméně obnovitelné funkce C ++ (aka „async a await“), které byly navrženy pro C ++ 17, budou pravděpodobně používat bezskládací coutiny.) Připravuje se návrh vláken do standardní knihovny C ++. Existují také některé knihovny třetích stran. Zelená vlákna jsou extrémně populární v jazycích jako Python a Ruby. | Mám něco, o co se mohu podělit, i když hlavní body jsou již pokryty. Zásobník Velmi rychlý přístup. Uloženo v RAM. Zde se načítají volání funkcí spolu s předanými lokálními proměnnými a funkčními parametry. Když program vyjde z rozsahu, automaticky se uvolní místo. Uloženo v sekvenční paměti. Halda Pomalý přístup srovnatelně se Stackem. Uloženo v RAM. Zde se ukládají dynamicky vytvářené proměnné, což později vyžaduje uvolnění přidělené paměti po použití. Uloženo všude tam, kde se provádí alokace paměti, vždy k němu přistupuje ukazatel. Zajímavá poznámka: Pokud by volání funkcí byla uložena v haldě, mělo by to za následek 2 chaotické body: Díky postupnému ukládání v zásobníku je provádění rychlejší. Skladování na hromadě by mělo za následek obrovskou spotřebu času, což by zpomalilo provádění celého programu. Pokud by byly funkce uloženy v haldě (chaotické úložiště ukazované ukazatelem), nebylo by možné vrátit se zpět na adresu volajícího (což zásobník dává kvůli sekvenčnímu ukládání do paměti). | Wow! Tolik odpovědí a nemyslím si, že jeden z nich to pochopil správně ... 1) Kde a co jsou (fyzicky v paměti skutečného počítače)? Zásobník je paměť, která začíná jako nejvyšší adresa paměti přidělená obrazu vašeho programu, a odtud pak klesá její hodnota. Je vyhrazeno pro volané funkční parametry a pro všechny dočasné proměnné používané ve funkcích. Existují dvě hromady: veřejná a soukromá. Soukromá hromada začíná na hranici 16 bajtů (pro 64bitové programy) nebo na 8bajtové hranici (pro 32bitové programy) po posledním bajtu kódu ve vašem programu a poté se zvyšujehodnota odtud. Nazývá se také výchozí halda. Pokud je soukromá hromada příliš velká, překryje oblast zásobníku, stejně jako hromádka překryje hromadu, pokud bude příliš velká. Vzhledem k tomu, že zásobník začíná na vyšší adrese a postupuje dolů na nižší adresu, při správném hackování můžete vytvořit zásobník tak velký, že překročí soukromou oblast haldy a překryje oblast kódu. Trik pak spočívá v dostatečném překrytí oblasti kódu, kterou můžete do kódu připojit. Je to trochu složité a riskujete selhání programu, ale je to snadné a velmi efektivní. Veřejná hromada se nachází ve vlastním paměťovém prostoru mimo prostor obrazu vašeho programu. Právě tato paměť bude sifonována na pevný disk, pokud dojde k nedostatku paměťových zdrojů. 2) Do jaké míry jsou řízeny operačním systémem nebo jazykovým modulem runtime? Zásobník je řízen programátorem, soukromá halda je spravována OS a veřejná halda není nikým řízena, protože se jedná o službu OS - zadáváte žádosti a jsou buď povoleny, nebo odmítnuty. 2b) Jaký je jejich rozsah? Všechny jsou pro program globální, ale jejich obsah může být soukromý, veřejný nebo globální. 2c) Co určuje velikost každého z nich? Velikost zásobníku a soukromé haldy jsou určeny možnostmi runtime kompilátoru. Veřejná halda je inicializována za běhu pomocí parametru velikosti. 2d) Čím je člověk rychlejší? Nejsou navrženy tak, aby byly rychlé, jsou navrženy tak, aby byly užitečné. Jak je programátor využívá, určuje, zda jsou „rychlé“ nebo „pomalé“ REF: https://norasandler.com/2019/02/18/Write-a-Compiler-10.html https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate | Mnoho odpovědí je správných jako koncepty, ale musíme si uvědomit, že hardware (tj. Mikroprocesor) potřebuje zásobník, který umožní volání podprogramů (CALL v assembleru ..). (OOP kluci tomu říkají metody) V zásobníku uložíte zpáteční adresy a volání → push / ret → pop je spravováno přímo v hardwaru. Zásobník můžete použít k předání parametrů .. i když je pomalejší než použití registrů (řekl by to mikroprocesorový guru nebo dobrá kniha BIOS z 80. let ...) Bez zásobníku nemůže fungovat žádný mikroprocesor. (nemůžeme si představit program, dokonce ani v assembleru, bez podprogramů / funkcí) Bez hromady to může. (Program v assembleru může fungovat bez, protože halda je koncept OS, jako malloc, což je volání OS / Lib. Využití zásobníku je rychlejší, protože: Je hardware, a dokonce i push / pop jsou velmi efektivní. malloc vyžaduje vstup do režimu jádra, použití zámku / semaforu (nebo jiných synchronizačních primitiv) provádějících nějaký kód a spravovat některé struktury potřebné ke sledování přidělování. | Halda je oblast dynamicky přidělené paměti, která je automaticky spravována operačním systémem nebo knihovnou správce paměti. Blok můžete kdykoli přidělit a kdykoli uvolnit. Alokace haldy vyžaduje udržování úplného záznamu o tom, co je alokována paměť a co ne, stejně jako nějakou údržbu režie ke snížení fragmentace, hledání souvislých segmentů paměti dostatečně velkých, aby se vešly na požadovanou velikost atd. Paměť lze kdykoli uvolnit a ponechat volné místo. Jak hromada roste, nové bloky se často přidělují od nižších adres k vyšším adresám. Haldu tedy můžete považovat za hromadu paměťových bloků, které se při přidělování paměti zvětšují. Pokud je halda pro přidělení příliš malá, lze velikost často zvýšit získáním více paměti ze základního operačního systému. Paměť přidělená z haldy zůstane přidělena, dokud nenastane jedna z následujících situací: Paměť je uvolněna Program končí Zásobník: Uloženo v paměti RAM počítače jako hromada. Proměnné vytvořené v zásobníku vyjdou z rozsahu a budou automaticky uvolněny. Mnohem rychlejší alokace ve srovnání s proměnnými na haldě. Ukládá místní data, zpáteční adresy, slouží k předávání parametrů. Může dojít k přetečení zásobníku, když se použije příliš mnoho zásobníku (většinou z nekonečné nebo příliš hluboké rekurze, velmi velké alokace). Zásobník byste použili, pokud přesně víte, kolik dat potřebujete přidělit před časem kompilace a není příliš velký. Obvykle má maximální velikost již určenou při vašem programu začíná. Halda: Uloženo v RAM počítače stejně jako zásobník. V C ++ musí být proměnné na haldě zničeny ručně a nikdy vypadnout z rozsahu. Data jsou uvolněna pomocí mazání, mazání [] nebo zdarma. Pomalejší alokace ve srovnání s proměnnými v zásobníku. Používá se na vyžádání k přidělení bloku dat pro použití programem. Může mít fragmentaci, když existuje mnoho alokací a uvolnění. V C ++ nebo C budou data vytvořená na haldě ukazována ukazateli a přidělené novým nebo malloc. Může dojít k selhání alokace, pokud je požadována příliš velká vyrovnávací paměť být přiděleno. Vyhaldu by použil, pokud nevíte přesně, kolik dat máte budete potřebovat za běhu nebo pokud potřebujete alokovat velké množství dat. Odpovědný za úniky paměti. | Zásobník je v podstatě snadno přístupná paměť, která jednoduše spravuje své položky jako - dobře - stack. Do zásobníku mohou přejít pouze předměty, u nichž je předem známa jejich velikost. To je případ čísel, řetězců, booleovců. Halda je pamětí na položky, které nemůžete předem určit přesná velikost a struktura. Protože objekty a pole lze mutovat a změnit za běhu, musí jít do haldy. Zdroj: Academind | Zásobník a hromada CPU fyzicky souvisí s tím, jak CPU a registry fungují s pamětí, jak funguje jazyk pro sestavování strojů, nikoli samotné jazyky na vysoké úrovni, i když tyto jazyky mohou rozhodovat o maličkostech. Všechny moderní CPU pracují se „stejnou“ teorií mikroprocesorů: všechny jsou založeny na tzv. „Registrech“ a některé jsou určeny pro „zásobník“ pro zvýšení výkonu. Všechny procesory mají od začátku zásobní registry a jak vím, byly vždy tady, jak mluvíme. Montážní jazyky jsou od začátku stejné, navzdory variacím ... až k Microsoftu a jeho intermediálnímu jazyku (IL), který změnil paradigma tak, aby měl montážní jazyk virtuálního stroje OO. Takže v budoucnu budeme moci mít nějaký procesor CLI / CIL (jeden projekt MS). CPU mají zásobní registry pro urychlení přístupu do paměti, ale jsou omezené ve srovnání s použitím jiných registrů k získání plného přístupu ke všem dostupným pamětem pro processus. Proto jsme mluvili o přidělení zásobníku a haldy. Souhrnně a obecně je halda shlukovaná a pomalá a je pro „globální“ instance a obsah objektů, protože zásobník je malý a rychlý a pro „místní“ proměnné a odkazy (skryté ukazatele, které je zapomenout spravovat). Takže když v metodě použijeme nové klíčové slovo, vytvoří se v zásobníku odkaz (int), ale objekt a veškerý jeho obsah (typy hodnot i objekty) se vytvoří v haldě, pokud si pamatuji. Ale místní elementární typy hodnot a pole jsou vytvořeny v zásobníku. Rozdíl v přístupu do paměti je na úrovni odkazů na buňky: adresování haldy, celkové paměti procesu, vyžaduje větší složitost zpracování registrů CPU, než zásobník, který je „více“ lokálně z hlediska adresování, protože zásobník CPU registr se používá jako základní adresa, pokud si pamatuji. To je důvod, proč když máme velmi dlouhé nebo nekonečné opakované hovory nebo smyčky, rychle jsme přeplnili zásobník, aniž bychom zmrazili systém na moderních počítačích ... C # Heap (ing) Vs Stack (ing) v .NET Stack vs Heap: Znát rozdíl Statické přidělení paměti třídy, kde je uložena C # Co a kde je hromádka a hromada? https://en.wikipedia.org/wiki/Memory_management https://en.wikipedia.org/wiki/Stack_register Prostředky jazyka shromáždění: Výukový program pro sestavování Příručky pro vývojáře softwaru Intel® 64 a IA-32 Architectures | Děkuji za opravdu dobrou diskusi, ale jako skutečný noob se ptám, kde jsou uloženy pokyny? V ZAČÁTKU se vědci rozhodovali mezi dvěma architekturami (von NEUMANN, kde je vše považováno za DATA a HARVARD, kde byla oblast paměti vyhrazena pro instrukce a další pro data). Nakonec jsme šli s designem von Neumanna a nyní je vše považováno za „stejné“. To mi ztěžovalo, když jsem se učil montáž https://www.cs.virginia.edu/~evans/cs216/guides/x86.html protože mluví o registrech a ukazatelích zásobníku. Všechno výše hovoří o DATA. Můj odhad je, že protože instrukce je definovaná věc se specifickou paměťovou stopou, šla by na zásobník, a tak všechny ‚ty 'registry diskutované v sestavě jsou na zásobníku. Samozřejmě pak přišlo objektově orientované programování s instrukcemi a daty přicházejícími do struktury, která byla dynamická, takže nyní by se instrukce uchovávaly také na hromadě? | Vysoce aktivní otázka. Získejte 10 reputace, abyste mohli odpovědět na tuto otázku. Požadavek na reputaci pomáhá chránit tuto otázku před spamem a neodpovědností. Toto není odpověď, kterou hledáte? Přečtěte si další otázky týkající se značek stack-memory-management stack language-agnostic haldy dynamic-memory-allocation nebo si položte vlastní otázku.